package org.aerie.forest.extensions.base.rebar.tool.math;

import java.math.BigDecimal;
import java.math.MathContext;

import org.aerie.forest.core.brick.exception.ExceptionPack;
import org.aerie.forest.core.brick.exception.custom.runtime.AttackedByReflexesException;
import org.aerie.forest.core.frame.rebar.entity.tool.ForestTool;
import org.aerie.forest.extensions.base.brick.exception.MathParaException;

/**
 * @description 数学工具【实数范围之内】
 *
 * @author quark
 * @organization aerie
 * @date 2020年4月5日 下午9:47:25
 * @version 1.2.0
 */
public final class MathTool extends ForestTool {

	/**
	 * @description 单例
	 * 
	 * @author quark
	 * @date 2020年3月29日 下午2:02:22
	 */
	private static volatile MathTool mathTool;

	/**
	 * Construction
	 * 
	 *
	 * @date 2020年3月29日 下午2:02:33
	 */
	private MathTool() {
		super("文件工具");
		LOGGER.info(LogMsgPool.INIT_COMPLETE , forestFrameName);
	}

	/**
	 * @description 获得单例文件工具
	 * @return 文件工具
	 *
	 * @date 2020年2月28日 下午12:44:00
	 */
	static final MathTool getInstance() {
		/**
		 * 双重锁
		 */
		if (mathTool == null) {
			synchronized (MathTool.class) {
				if (mathTool == null) {
					mathTool = new MathTool();
				}
			}
		}
		return mathTool;
	}

	/**
	 * @description 取两个正整数最大公约数
	 * @param num1 第一个正整数
	 * @param num2 第二个正整数
	 * @return 最大公约数
	 * @throws Exception 算法异常
	 *
	 * @date 2020年4月10日 上午10:08:13
	 */
	public long greatestCommonDivisor(long num1, long num2) throws Exception {
		if (num1 < 1 || num2 < 1) {
			throw new MathParaException("只有正整数能求出最大公约数");
		}
		if (num1 % num2 == 0)
			return num2;
		// 递归调用
		else
			return greatestCommonDivisor(num2, num1 % num2);
	}

	/**
	 * @description 取两个正整数最小公倍数
	 * @param num1 第一个正整数
	 * @param num2 第二个正整数
	 * @return 最小公倍数
	 * @throws Exception 算法异常
	 *
	 * @date 2020年4月13日 上午1:33:39
	 */
	public long lowestCommonMultiple(long num1, long num2) throws Exception {
		return num1 * num2 / greatestCommonDivisor(num1, num2);
	}

	/**
	 * @description 求实数的整数次方【并保留相应的小数位数】
	 * @param bigDecimal 底数
	 * @param exponent   指数
	 * @param scale      精确的小数位数
	 * @return 求得的值
	 *
	 * @date 2020年4月13日 上午1:50:55
	 */
	public BigDecimal powByExponentWithScale(BigDecimal bigDecimal, int exponent, int scale) {
		return bigDecimal.pow(exponent).setScale(scale);
	}

	/**
	 * @description 求实数的整数开根【并保留相应的小数位数】[scale默认最小为零]
	 * @param base         底数
	 * @param exponent     指数
	 * @param scale        精确的小数位数
	 * @param roundingMode 近似规则
	 * @return 求得的值
	 * @throws Exception
	 *
	 * @date 2020年4月13日 上午9:25:02
	 */
	public BigDecimal sqrtByExponentWithScale(BigDecimal base, int exponent, int scale, int roundingMode)
			throws Exception {
		LOGGER.debug(new StringBuilder("开始对").append(base).append("进行").append(exponent).append("次开根，").append("精度为")
				.append(scale).append("，近视值规则为").append(roundingMode).toString());
		LOGGER.debug("开始校验开方参数合法性。");
		try {
			if (exponent == 0)
				throw new MathParaException("开根指数不能为零");
			if (base.compareTo(BigDecimal.ZERO) < 0 && exponent % 2 == 0)
				throw new MathParaException("实数范围内不存在负数的偶次方根");
			if (roundingMode < 0 || roundingMode > 7)
				throw new MathParaException("近似规则不合法");
			if (scale < 0) {
				scale = 0;
				LOGGER.warn("开根算法精度不能小于零,精度被默认成零");
			}
			BigDecimal result = new BigDecimal(String.valueOf(Math.pow(base.doubleValue(), 1 / (double) exponent)))
					.setScale(scale, roundingMode);
			// 本地方法的精度满足需要直接返回
			if (scale < 15) {
				LOGGER.debug("本地方法精度达到要求，直接返回。");
				return result;
			}
			// 精准计算
			LOGGER.debug("本地方法精度达不到要求，开始进行精准计算。");
			LOGGER.trace("计算精准的结果的符号");
			boolean sign = (exponent % 2 == 0) ? true : base.compareTo(BigDecimal.ZERO) > 0;
			SqrtDynamicScalePack sqrtDynamicScalePack = new SqrtDynamicScalePack(base.abs(), exponent);
			for (;;) {
				System.err.println(sqrtDynamicScalePack.getDynamicScaleLayer() + "      " + scale);
				BigDecimal baseCompare = result.pow(exponent).setScale(sqrtDynamicScalePack.getDynamicScaleLayer() + 1,
						roundingMode);
				System.err.println(baseCompare + "        " + base);
				if (baseCompare.compareTo(base) < 0) {
					System.err.println("111111        " + sqrtDynamicScalePack.toString());
					result = result
							.add(sqrtDynamicScalePack.getDynamicOffsetOfX()
									.divide(base.subtract(baseCompare)
											.divide(sqrtDynamicScalePack.getDynamicOffsetOfY(), new MathContext(2)),
											new MathContext(1)));
					System.err.println();
				} else if (baseCompare.compareTo(base.add(SqrtDynamicScalePack
						.getDynamicScaleByLayer(sqrtDynamicScalePack.getDynamicScaleLayer()))) > 0) {
					System.err.println("222222        " + sqrtDynamicScalePack.toString());
					result = result
							.subtract(sqrtDynamicScalePack.getDynamicOffsetOfX()
									.divide(baseCompare.subtract(base)
											.divide(sqrtDynamicScalePack.getDynamicOffsetOfY(), new MathContext(2)),
											new MathContext(1)));
				} else if (sqrtDynamicScalePack.getDynamicScaleLayer() >= scale) {
					System.err.println("333333333");
					break;
				} else {
					System.err.println("4444444444");
					sqrtDynamicScalePack.promoteScaleLayer();
				}
			}
			return result;
		} catch (Exception e) {
			throw new ExceptionPack(
					new StringBuilder("实数:").append(base).append("开").append(exponent).append("次根异常").toString(), e);
		}
	}

	/**
	 * @description 排列
	 * @param m 排列的元素的数
	 * @param n 元素总数
	 * @return 排列的总数
	 *
	 * @date 2020年4月13日 上午11:25:03
	 */
	public int arrangement(int m, int n) throws Exception {
		try {
			verifyfACParam(m, n);
		} catch (Exception e) {
			throw new ExceptionPack("排列计算异常", e);
		}
		return factorial(n) / factorial(n - m);
	}

	/**
	 * @description 组合
	 * @param m 组合的元素的数
	 * @param n 元素总数
	 * @return 组合的总数
	 *
	 * @date 2020年4月13日 上午11:25:03
	 */
	public int combination(int m, int n) throws Exception {
		try {
			verifyfACParam(m, n);
		} catch (Exception e) {
			throw new ExceptionPack("组合计算异常", e);
		}
		return factorial(n) / factorial(n - m) / factorial(m);
	}

	/**
	 * @description 校验排列组合的参数合法性
	 * @param m 排列的元素的数
	 * @param n 排列的总数
	 * @throws Exception 参数不合法异常
	 *
	 * @date 2020年4月13日 上午11:35:53
	 */
	private void verifyfACParam(int m, int n) throws Exception {
		if (m < 0)
			throw new MathParaException("排列的元素的数不能小于零");
		if (n <= 0)
			throw new MathParaException("元素总数必须大于零");
		if (m > n)
			throw new MathParaException("排列的元素的数不能大于元素总数");
	}

	/**
	 * @description 计算阶乘
	 * @param num 阶乘因子
	 * @return 阶乘的结果
	 * @throws Exception 阶乘因不合法
	 *
	 * @date 2020年4月13日 下午12:16:43
	 */
	public int factorial(int num) throws Exception {
		if (num < 0)
			throw new MathParaException("阶乘的因子不能小于0");
		if (num == 0)
			return 0;
		int result = 1;
		for (int index = 2; index <= num; index++) {
			result *= index;
		}
		return result;
	}
}
